# -*- coding: utf-8 -*-
# -*- frozen_string_literal: true -*-

require "rbconfig"

module REPL
  FG_COLOR_ON= "\e[38;5;"
  COLOR_OFF = "\e[0m"
  COLORS = { # ref: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
    red: "#{FG_COLOR_ON}1m",
    green: "#{FG_COLOR_ON}2m",
    yellow: "#{FG_COLOR_ON}220m",
    blue: "#{FG_COLOR_ON}33m",
    magenta: "#{FG_COLOR_ON}129m",
    lightgenta: "#{FG_COLOR_ON}164m",
    violet: "#{FG_COLOR_ON}147m",
    cyan: "#{FG_COLOR_ON}6m"
  }.freeze

  def self.modules
    ["irb/completion", "irb/ext/save-history"]
  end

  def self.readline
    true
  end

  def self.history
    "~/.irb_history"
  end

  def self.history_size
    50
  end

  def self.session_name
    "repl"
  end

  def self.auto_indent
    true
  end

  def self.prompts
    non_padded_prompts.merge(padded_prompts).freeze
  end

  def self.contextual_prompts
    {
      PROMPT_I: prompts[:info],
      PROMPT_S: prompts[:quote_missing],
      PROMPT_C: prompts[:closing_missing],
      PROMPT_N: prompts[:scoped],
      RETURN: prompts[:return]
    }.freeze
  end

  class << self
    private

    PADDING = " "
    LINE_NUM = "%n"
    STR_LIMIT = "%l#{PADDING}"
    MISSING_LIMIT = "?#{PADDING}"
    INDENT_LIMIT = "|#{PADDING * 2}"
    CONTEXT = "::%m#{PADDING}"
    CONTEXT_HEAD = ">#{PADDING}"
    REPL_NAME = "%N" #=> IRB_NAME
    RET_PROMPT = "#=>#{PADDING}"
    AT = "@"
    EVALED = ENV["PAGER"] ? "%s\n" : "%.2048s\n" # all : truncated to N chars

    def non_padded_prompts
      {
        info: prompt_body + CONTEXT + LINE_NUM + CONTEXT_HEAD + COLOR_OFF,
        return: COLORS[:cyan] + RET_PROMPT + COLOR_OFF + EVALED
      }.freeze
    end

    def padded_prompts
      {
        quote_missing: pad_prompt(STR_LIMIT),
        closing_missing: pad_prompt(MISSING_LIMIT, color: COLORS[:yellow]),
        scoped: pad_prompt(INDENT_LIMIT)
      }.freeze
    end

    def pad_prompt prompt_head, color:COLORS[:magenta]
      indentation = PADDING * (prompt_body.length - 1)
      indentation + color + LINE_NUM + prompt_head + COLOR_OFF
    end

    def prompt_body
      app = File.basename Dir.pwd if File.exist?("Gemfile") || File.exist?("gems.rb")
      app = app ? app : REPL_NAME
      app = COLORS[:blue] + app

      env = ENV["RACK_ENV"].to_s.sub("production", "prod").sub("development", "dev")
      unless env.empty?
        at = COLORS[:yellow] + AT
        colored = env.match?("prod") ? COLORS[:red] : COLORS[:green]
        env = at + colored + env
      end

      app + env
    end
  end # end private class methods

  def _rbv
    RbConfig::CONFIG["RUBY_PROGRAM_VERSION"]
  end

  def _mths obj, grep:nil
    return puts "grep must be a Regexp" unless grep.nil? || grep.kind_of?(Regexp)
    methods = ( obj.methods - obj.class.superclass.instance_methods ).sort
    methods = methods.select { |name| name =~ grep } if grep
    instance_method_identifier = "#"
    info = methods.collect do |sym|
      method = obj.method sym
      arity = method.arity
      body = method.to_s.chars[0...-1].join.split(" ").last.split(instance_method_identifier)

      args = arity.zero? ? "" : (1..arity.abs).collect {|i| " arg_#{i}"}.join(",")
      args += ", *args" if arity < 0
      body += [args]
    end

    mth_id = obj.respond_to?(:new) ? "" : instance_method_identifier
    max_name = info.collect {|i| i[0].size}.max
    info.each do |item|
      print COLORS[:lightgenta] + item[0].rjust(max_name) + COLORS[:violet] + mth_id
      print COLORS[:blue] + item[1].ljust(1)
      puts "#{COLORS[:cyan]}#{item[2]}#{COLOR_OFF}"
    end

    info.size
  end

  def _clr
    system "clear"
  end

  def _aka
    {
      q: :irb_quit,
      ri: :irb_help,
      ls: :irb_jobs,
      cd: :irb,
      go: :irb_fg,
      kl: :irb_kill
    }.each { |k, v| install_alias_method k, v }
  end

  def _toggle_return
    IRB.CurrentContext.echo = !IRB.CurrentContext.echo
  end

  def _toggle_tracer
    return IRB.CurrentContext.use_tracer = true unless defined? IRB.CurrentContext.use_tracer
    IRB.CurrentContext.use_tracer = !IRB.CurrentContext.use_tracer
  end

  def _cc
    IRB.CurrentContext
  end

  # def _pst
  #   `pbpaste` if RbConfig::CONFIG['host_os'].include?(%r"darwin"i)
  # end
end


if __FILE__ == $PROGRAM_NAME
  require "minitest/autorun"
  require "minitest/hell"
  require "minitest/pride"
  require "minitest/proveit"

  class Fake
    def a
    end

    def b arg
    end

    def c ar, *args
    end
  end
  class TestREPL < Minitest::Test
    def setup
      @repl = Object.new
      @repl.extend REPL
    end

    def test_info_prompt
      ENV["RACK_ENV"] = ["production", "development", nil].sample

      case ENV["RACK_ENV"]
      when "production"
        assert_match %r".*%N.*@.*prod::%m\s%n>\s.*", REPL.prompts[:info]
      when "development"
        assert_match %r".*%N.*@.*dev::%m\s%n>\s.*", REPL.prompts[:info]
      else
        assert_match %r".*%N::%m\s%n>\s.*", REPL.prompts[:info]
      end
    end

    def test_scoped_prompt
      assert_match %r".*%n|\s*.*", REPL.prompts[:scoped]
    end

    def test_quote_missing_prompt
      assert_match %r".*%n%l\s.*", REPL.prompts[:quote_missing]
    end

    def test_closing_missing_prompt
      assert_match %r".*%n\?\s.*", REPL.prompts[:closing_missing]
    end

    def test_repl_return_prompt
      assert_match %r".*#=>\s.*%.*s\n", REPL.prompts[:return]
      # assert_match %r".*#=>\s.*%.*s\n", REPL.return
    end

    def test_ruby_version
      repl = Object.new.extend REPL
      out, _ = capture_subprocess_io do
        repl._rbv
      end
      assert_equal "#{RUBY_VERSION}", out.chomp
    end

    def test_instance_methods
      method_count = nil
      rpl = Object.new.extend REPL
      out, _ = capture_subprocess_io { method_count = rpl._mths Fake.new }

      assert_match %r/.*Fake.*\#.*a.*\n.*Fake.*\#.*b.*arg_1.*\n.*Fake.*\#.*c.*\sarg_1,\sarg_2,\s\*args.*/, out
      assert_equal 3, method_count
    end

    def test_grep_instance_methods
      rpl = Object.new.extend REPL
      out, _ = capture_subprocess_io { rpl._mths Fake.new, grep: /a/ }

      assert_match %r".*Fake.*#.*a.*", out
    end

    def test_clear_screen
      rpl = Object.new.extend REPL
      out, _ = capture_subprocess_io { rpl._clr }

      assert_match %r/[[:cntrl:]]/, out.chomp
    end

    def test__auto_indent
      assert REPL.auto_indent
    end

    def test__session_name
      assert_equal "repl", REPL.session_name
    end

    def test__history_size
      assert_equal 50, REPL.history_size
    end

    def test___history
      assert_equal "~/.irb_history", REPL.history
    end

    def test__readline
      assert REPL.readline
    end

    def test__modules
      assert_equal %w[irb/completion irb/ext/save-history], REPL.modules
    end

    def test_aka
      rpl = Object.new.extend REPL
      assert_respond_to rpl, :_aka
    end

    def test_toggle_return_echo
      rpl = Object.new.extend REPL
      assert_respond_to rpl, :_toggle_return
    end

    def test_toggle_tracer
      rpl = Object.new.extend REPL
      assert_respond_to rpl, :_toggle_tracer
    end

    def test_display_current_context_settings
      rpl = Object.new.extend REPL
      assert_respond_to rpl, :_cc
    end
  end
end
